﻿namespace Centido.Core.Test
{
    using System.Collections.Generic;
    using System.Data.Common;
    using System.Linq;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    /// <summary>
    /// Enumeration table column testing
    /// </summary>
    [TestClass]
    public class EnumColumnTest : ColumnBaseTest<int?>
    {
        /// <summary>
        /// Enumeration table column
        /// </summary>
        private EnumColumn Column
        {
            get { return GenericColumn as EnumColumn; }
        }

        /// <summary>
        /// Another enumeration table column
        /// </summary>
        private EnumColumn AnotherColumn 
        {
            get { return AnotherGenericColumn as EnumColumn; }
        }

        /// <summary>
        /// Get column type
        /// </summary>
        /// <returns></returns>
        protected override ColumnType GetColumnType()
        {
            return ColumnType.Enumeration;
        }

        /// <summary>
        /// Get large value
        /// </summary>
        /// <returns></returns>
        protected override int? GetLargeValue()
        {
            if (Column.GetMember(5) == null)
            {
                Column.AddMember(5);
                AnotherColumn.AddMember(5);
            }

            return 5;
        }

        /// <summary>
        /// Get small column
        /// </summary>
        /// <returns></returns>
        protected override int? GetSmallValue()
        {
            if (Column.GetMember(2) == null)
            {
                Column.AddMember(2);
                AnotherColumn.AddMember(2);
            }

            return 2;
        }

        /// <summary>
        /// Convert db value
        /// </summary>
        /// <param name="dbValue">Db value</param>
        /// <returns></returns>
        protected override int? ConvertDbValue(object dbValue)
        {
            return DbValue.GetInteger(dbValue);
        }

        /// <summary>
        /// Creating enumeration member
        /// </summary>
        [TestMethod]
        public void CreatingMember()
        {
            //Initially there must be no members
            Assert.AreEqual(Column.Members.Count(), 0);

            //Creating new member
            var member = Column.AddMember(1);

            Assert.AreEqual(Column.Members.Count(), 1);
            Assert.IsNotNull(Column.GetMember(1));
            Assert.AreEqual(Column.GetMember(1).Value, 1);
            Assert.IsTrue(Column.Members.Any(m => m.Value == 1));

            Assert.IsNotNull(member);
            Assert.IsTrue(member.Exists());
            Assert.AreEqual(1, member.Value);

            //Creating another member
            Column.AddMember(2);

            Assert.AreEqual(Column.Members.Count(), 2);
            Assert.IsNotNull(Column.GetMember(2));
            Assert.AreEqual(Column.GetMember(2).Value, 2);
            Assert.IsTrue(Column.Members.Any(m => m.Value == 2));
        }

        /// <summary>
        /// Creating large number of members
        /// </summary>
        [TestMethod]
        public void CreatingLargeNumberOfMembers()
        {
            for (var i = 0; i < 250; i++)
            {
                Column.AddMember(1200000000 + i);
            }

            Assert.AreEqual(Column.Members.Count(), 250);
        }

        /// <summary>
        /// Creating duplicate enumeration member
        /// </summary>
        [TestMethod]
        public void CreatingDuplicateMember()
        {
            Column.AddMember(1);

            try
            {
                Column.AddMember(1);
            }
            catch (DbException e)
            {
                Assert.AreEqual(e.GetErrorCode(), DbErrorCode.AddingDuplicateEnumMember);
                return;
            }

            Assert.Fail();
        }

        /// <summary>
        /// Removing enumeration member
        /// </summary>
        [TestMethod]
        public void RemovingMember()
        {
            var firstMember = Column.AddMember(1);
            var secondMember = Column.AddMember(2);

            Assert.AreEqual(Column.Members.Count(), 2);

            //Removing the first member
            Column.DeleteMember(1);

            Assert.AreEqual(Column.Members.Count(), 1);
            Assert.IsNull(Column.GetMember(1));
            Assert.IsNotNull(Column.GetMember(2));
            
            Assert.IsFalse(firstMember.Exists());
            Assert.IsTrue(secondMember.Exists());

            //Removing the second member
            Column.GetMember(2).Delete();

            Assert.AreEqual(Column.Members.Count(), 0);
            Assert.IsNull(Column.GetMember(2));
        }

        /// <summary>
        /// Removing enumeration member that doesn't exist
        /// </summary>
        [TestMethod]
        public void RemovingMemberThatDoesntExist()
        {
            Column.AddMember(1);

            Assert.AreEqual(Column.Members.Count(), 1);

            //Getting that member
            var member = Column.GetMember(1);

            //Removing that member
            Column.DeleteMember(1);

            Assert.AreEqual(Column.Members.Count(), 0);

            try
            {
                //Removing that member once again
                member.Delete();
            }
            catch (DbException e)
            {
                Assert.AreEqual(e.GetErrorCode(), DbErrorCode.DeletingNonexistentEnumMember);
                return;
            }

            Assert.Fail();
        }

        /// <summary>
        /// Setting property of member that doesn't exist
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(DbOperationException))]
        public void SettingPropertyOfMemberThatDoesntExist()
        {
            Column.AddMember(1);

            Assert.AreEqual(Column.Members.Count(), 1);

            //Getting that member
            var member = Column.GetMember(1);

            //Setting property
            const string displayName = "Display name";

            member.DisplayName = displayName;

            Assert.AreEqual(member.DisplayName, displayName);

            //Removing that member
            Column.DeleteMember(1);

            Assert.AreEqual(Column.Members.Count(), 0);

            member.Refresh();

            Assert.IsNull(member.DisplayName);

            member.DisplayName = "Another name";
        }

        /// <summary>
        /// Inserting member value
        /// </summary>
        [TestMethod]
        public void InsertingMemberValue()
        {
            //Adding member
            Column.AddMember(1);

            Assert.AreEqual(Column.Members.Count(), 1);
            Assert.IsNotNull(Column.GetMember(1));
            Assert.AreEqual(Column.GetMember(1).Value, 1);

            Assert.AreEqual(Table.RowCount, 0);

            //Inserting member value
            var rowId = Table.InsertRow(GenericColumn.Value(1));

            Assert.AreEqual(Table.RowCount, 1);

            //Reading inserted value
            var inserted = GenericColumn.EmptyValue();

            Table.ReadRow(rowId, inserted);

            Assert.AreEqual(inserted.GetInteger(), 1);
        }

        /// <summary>
        /// Inserting not member value
        /// </summary>
        [TestMethod]
        public void InsertingNotMemberValue()
        {
            //Adding member
            Column.AddMember(1);

            Assert.AreEqual(Column.Members.Count(), 1);

            Assert.AreEqual(Table.RowCount, 0);

            try
            {
                //Inserting not member value
                Table.InsertRow(GenericColumn.Value(0));
            }
            catch (DbException)
            {
                Assert.AreEqual(Table.RowCount, 0);
                return;
            }

            Assert.Fail();
        }

        /// <summary>
        /// Inserting null when there are members
        /// </summary>
        [TestMethod]
        public void InsertingNullWhenThereAreMembers()
        {
            //Adding member
            Column.AddMember(1);

            Assert.AreEqual(Column.Members.Count(), 1);

            //Inserting null
            var rowId = Table.InsertRow(GenericColumn.Value(null));

            Assert.AreEqual(Table.RowCount, 1);

            //Reading inserted value
            var inserted = GenericColumn.EmptyValue();

            Table.ReadRow(rowId, inserted);

            Assert.IsNull(inserted.GetInteger());
        }

        /// <summary>
        /// Inserting null when there are no members
        /// </summary>
        [TestMethod]
        public void InsertingNullWhenThereAreNoMembers()
        {
            Assert.AreEqual(Column.Members.Count(), 0);

            //Inserting null
            var rowId = Table.InsertRow(GenericColumn.Value(null));

            Assert.AreEqual(Table.RowCount, 1);

            //Reading inserted value
            var inserted = GenericColumn.EmptyValue();

            Table.ReadRow(rowId, inserted);

            Assert.IsNull(inserted.GetInteger());
        }

        /// <summary>
        /// Deleting member when values were inserted
        /// </summary>
        [TestMethod]
        public void DeletingMemberWhenValuesWereInserted()
        {
            //Adding two members
            Column.AddMember(1);
            Column.AddMember(2);

            Assert.AreEqual(Column.Members.Count(), 2);

            //Inserting first member value
            Table.InsertRow(GenericColumn.Value(1));

            //Inserting second member value
            Table.InsertRow(GenericColumn.Value(2));

            Assert.AreEqual(Table.RowCount, 2);

            //Deleting second member value
            Column.DeleteMember(2);

            Assert.AreEqual(Column.Members.Count(), 1);

            Assert.AreEqual(Table.RowCount, 2);

            //Reading inserted values
            var insertedValues = new List<int?>();

            GenericColumn.ReadAll(val => insertedValues.Add(val.GetInteger()));

            Assert.AreEqual(insertedValues.Count, 2);
            Assert.AreEqual(insertedValues.Count(v => v == 1), 1);
            Assert.AreEqual(insertedValues.Count(v => v == null), 1);
        }

        /// <summary>
        /// Deleting member when value was deleted
        /// </summary>
        [TestMethod]
        public void DeletingMemberWhenValueWasDeleted()
        {
            //Adding member
            Column.AddMember(1);

            Assert.AreEqual(Column.Members.Count(), 1);

            //Inserting member value
            var rowId = Table.InsertRow(GenericColumn.Value(1));

            Assert.AreEqual(Table.RowCount, 1);

            //Deleting member value
            Table.DeleteRow(rowId);

            Assert.AreEqual(Table.RowCount, 0);

            //Removing member
            Column.DeleteMember(1);

            Assert.AreEqual(Column.Members.Count(), 0);
        }

        /// <summary>
        /// Deleting member when value was inserted and is not null
        /// </summary>
        [TestMethod]
        public void DeletingMemberWhenValueWasInsertedAndIsNotNull()
        {
            Column.IsNotNull = true;

            //Adding member
            Column.AddMember(1);

            Assert.AreEqual(Column.Members.Count, 1);

            //Inserting member value
            Table.InsertRow(GenericColumn.Value(1));

            Assert.AreEqual(Table.RowCount, 1);

            try
            {
                //Deleting member 
                Column.DeleteMember(1);
            }
            catch (DbException)
            {
                Assert.AreEqual(Column.Members.Count, 1);
                return;
            }

            Assert.Fail();
        }

        /// <summary>
        /// Deleting member when value was deleted and is not null
        /// </summary>
        [TestMethod]
        public void DeletingMemberWhenValueWasDeletedAndIsNotNull()
        {
            Domain.HasRecycleBin = true;

            Column.IsNotNull = true;

            //Adding member
            Column.AddMember(1);

            Assert.AreEqual(Column.Members.Count, 1);

            //Inserting member value
            var rowId = Table.InsertRow(GenericColumn.Value(1));

            Assert.AreEqual(Table.RowCount, 1);

            //Deleting member value
            Table.DeleteRow(rowId);

            Assert.AreEqual(Table.RowCount, 0);

            try
            {
                //Deleting member 
                Column.DeleteMember(1);
            }
            catch (DbException)
            {
                Assert.AreEqual(Column.Members.Count, 1);
                return;
            }

            Assert.Fail();
        }

        /// <summary>
        /// Reading and writing member display name
        /// </summary>
        [TestMethod]
        public void ReadingAndWritingMemberDisplayName()
        {
            //Adding member
            var member = Column.AddMember(1);

            //Initially display name must be null
            Assert.IsNull(member.DisplayName);

            //Setting display name
            var displayName = StringProperty.StandardValue('A');

            member.DisplayName = displayName;

            Assert.AreEqual(member.DisplayName, displayName);

            //Setting display name to null
            member.DisplayName = null;

            Assert.IsNull(member.DisplayName);
        }

        /// <summary>
        /// Checking that member display name is trimmed
        /// </summary>
        [TestMethod]
        public void CheckingThatMemberDisplayNameIsTrimmed()
        {
            //Adding member
            var member = Column.AddMember(1);

            member.DisplayName = "\t A\t ";

            Assert.AreEqual(member.DisplayName, "A");
        }

        /// <summary>
        /// Reading and writing member value
        /// </summary>
        [TestMethod]
        public void ReadingAndWritingMemberValue()
        {
            //Adding member
            var member = Column.AddMember(1);

            Assert.AreEqual(member.Value, 1);

            //Setting member value
            member.Value = 2;
            member.Value = 2;

            Assert.AreEqual(member.Value, 2);

            Assert.IsNotNull(Column.GetMember(2));
            Assert.IsNull(Column.GetMember(1));
            Assert.AreEqual(Column.Members.Count(), 1);
        }

        /// <summary>
        /// Setting member value to duplicate value
        /// </summary>
        [TestMethod]
        public void SettingMemberValueToDuplicateValue()
        {
            //Adding two members
            var member = Column.AddMember(1);
            Column.AddMember(2);

            try
            {
                //Setting value to duplicate value
                member.Value = 2;
            }
            catch (DbException)
            {
                Assert.AreEqual(member.Value, 1);
                return;
            }

            Assert.Fail();
        }

        /// <summary>
        /// Setting member value when values were inserted
        /// </summary>
        [TestMethod]
        public void SettingMemberValueWhenValuesWereInserted()
        {
            //Adding two members
            Column.AddMember(1);
            Column.AddMember(2);

            Assert.AreEqual(Column.Members.Count(), 2);

            //Inserting first member value
            Table.InsertRow(GenericColumn.Value(1));

            //Inserting second member value
            Table.InsertRow(GenericColumn.Value(2));

            Assert.AreEqual(Table.RowCount, 2);

            //Changing first member value
            Column.GetMember(1).Value = 3;

            //Reading inserted values
            var insertedValues = new List<int?>();

            GenericColumn.ReadAll(val => insertedValues.Add(val.GetInteger()));

            Assert.AreEqual(insertedValues.Count, 2);
            Assert.AreEqual(insertedValues.Count(v => v == 1), 0);
            Assert.AreEqual(insertedValues.Count(v => v == 2), 1);
            Assert.AreEqual(insertedValues.Count(v => v == 3), 1);
        }

        /// <summary>
        /// Setting default value that is not a member value
        /// </summary>
        [TestMethod]
        public void SettingDefaultValueThatIsNotMemberValue()
        {
            Assert.IsNull(Column.GetMember(1));

            try
            {
                Column.DefaultValue = 1;
            }
            catch (DbException)
            {
                Assert.IsNull(Column.DefaultValue);
                return;
            }

            Assert.Fail();
        }

        /// <summary>
        /// Removing member value that is default value
        /// </summary>
        [TestMethod]
        public void RemovingMemberValueThatIsDefaultValue()
        {
            Column.AddMember(1);

            Assert.IsNotNull(Column.GetMember(1));
            Assert.AreEqual(Column.GetMember(1).Value, 1);

            Column.DefaultValue = 1;

            Assert.AreEqual(Column.DefaultValue, 1);

            Column.GetMember(1).Delete();

            Assert.IsNull(Column.GetMember(1));

            Assert.IsNull(Column.DefaultValue);
        }

        /// <summary>
        /// Changing member value
        /// </summary>
        [TestMethod]
        public void ChangingMemberValue()
        {
            Column.AddMember(1);
            Column.AddMember(2);

            Assert.AreEqual(2, Column.Members.Count());

            //Inserting first member value
            Table.InsertRow(GenericColumn.Value(1));

            //Inserting second member value
            Table.InsertRow(GenericColumn.Value(2));

            Assert.AreEqual(2, Table.RowCount);

            //Changing first member value
            Column.GetMember(1).Value = 3;

            Assert.IsNotNull(Column.GetMember(3));

            //Reading members values
            var memberValues = new List<int?>();

            GenericColumn.ReadAll(val => memberValues.Add(val.GetInteger()));

            Assert.AreEqual(2, memberValues.Count);
            Assert.AreEqual(1, memberValues.Count(m => m == 2));
            Assert.AreEqual(1, memberValues.Count(m => m == 3));
            Assert.AreEqual(0, memberValues.Count(m => m == 1));
        }

        /// <summary>
        /// Changing member value that is default value
        /// </summary>
        [TestMethod]
        public void ChangingMemberValueThatIsDefaultValue()
        {
            Column.AddMember(1);

            Assert.IsNotNull(Column.GetMember(1));

            Column.DefaultValue = 1;

            //Changing default value
            Column.GetMember(1).Value = 2;

            Column.Refresh();

            Assert.IsNotNull(Column.GetMember(2));
            Assert.IsNull(Column.GetMember(1));

            Assert.AreEqual(Column.DefaultValue, 2);
        }


        /// <summary>
        ///   Reading and writing the enum value checking property.
        /// </summary>
        [TestMethod]
        public void ReadingAndWritingValueChecking()
        {
            Assert.IsTrue(Column.HasValueChecking);

            Column.HasValueChecking = false;
            Column.HasValueChecking = false;

            Assert.IsFalse(Column.HasValueChecking);

            Column.HasValueChecking = true;
            Column.HasValueChecking = true;

            Assert.IsTrue(Column.HasValueChecking);
        }


        /// <summary>
        ///   Inserting an invalid value when the value checking is off.
        /// </summary>
        [TestMethod]
        public void InsertingInvalidValueWhenCheckingIsOff()
        {
            Column.HasValueChecking = false;

            Table.InsertRow(Column.Value(500));

            Assert.AreEqual(Table.RowCount, 1);
        }


        /// <summary>
        ///   Trying to turn the value checking on after inserting an invalid value.
        /// </summary>
        [TestMethod]
        public void TryingToTurnCheckingOnAfterInsertingInvalidValue()
        {
            Column.HasValueChecking = false;

            Table.InsertRow(Column.Value(500));

            Assert.AreEqual(Table.RowCount, 1);

            try
            {
                Column.HasValueChecking = true;
            }
            catch (DbException)
            {
                Assert.IsFalse(Column.HasValueChecking);
                return;
            }

            Assert.Fail();
        }


        /// <summary>
        ///   Changing the value of the default member (bug fix).
        /// </summary>
        [TestMethod]
        public void ChangingValueOfDefaultMember()
        {
            var smallValue = GetSmallValue().GetValueOrDefault();
            var largeValue = GetLargeValue().GetValueOrDefault();

            Assert.IsNull(Column.DefaultValue);

            // Setting the default value
            Column.DefaultValue = smallValue;

            Assert.AreEqual(Column.DefaultValue, smallValue);

            // Changing the value of the default member
            Column.GetMember(smallValue).Value = largeValue + 1;

            Assert.AreEqual(Column.DefaultValue, largeValue + 1);
        }
    }
}